import time
from threading import Lock, Timer, currentThread
from time import time as gettime

import wx

MainThread = currentThread().ident
REFRESH_PERIOD = 0.1


class LogPseudoFile(object):
    """ Base class for file like objects to facilitate StdOut for the Shell."""

    def __init__(self, output, risecall, logf):
        self.red_white = 1
        self.red_yellow = 2
        self.black_white = wx.stc.STC_STYLE_DEFAULT
        self.output = output
        self.risecall = risecall
        # to prevent rapid fire on rising log panel
        self.rising_timer = 0
        self.StackLock = Lock()
        self.YieldLock = Lock()
        self.RefreshLock = Lock()
        self.TimerAccessLock = Lock()
        self.stack = []
        self.LastRefreshTime = gettime()
        self.LastRefreshTimer = None
        self.refreshPending = False
        self.logf = logf

    def write(self, s, style=None):
        if not isinstance(s, str): s = s.decode()
        if not s.endswith('\n'): s += '\n'
        if self.logf is not None:
            self.logf.write(s)
            self.logf.flush()
        self.StackLock.acquire()
        self.stack.append((s, style))
        self.StackLock.release()
        current_time = gettime()
        with self.TimerAccessLock:
            if self.LastRefreshTimer is not None:
                self.LastRefreshTimer.cancel()
                self.LastRefreshTimer = None
        elapsed = current_time - self.LastRefreshTime
        if elapsed > REFRESH_PERIOD:
            self._should_write()
        else:
            with self.TimerAccessLock:
                if self.LastRefreshTimer is None:
                    self.LastRefreshTimer = Timer(REFRESH_PERIOD - elapsed, self._timer_expired)
                    self.LastRefreshTimer.start()

    def _timer_expired(self):
        self._should_write()
        with self.TimerAccessLock:
            self.LastRefreshTimer = None

    def _should_write(self):

        if MainThread == currentThread().ident:
            app = wx.GetApp()
            if app is not None:
                self._write()
                if self.YieldLock.acquire(False):
                    app.Yield()
                    self.YieldLock.release()
        else:
            with self.RefreshLock:
                if not self.refreshPending:
                    self.refreshPending = True
                    wx.CallAfter(self._write)

    def _write(self):
        if self.output:
            with self.RefreshLock:
                self.output.Freeze()
                self.output.AnnotationClearAll()
                self.StackLock.acquire()
                for s, style in self.stack:
                    if style is None:
                        style = self.black_white
                    if style != self.black_white:
                        self.output.StartStyling(self.output.GetLength())

                    # Temporary deactivate read only mode on StyledTextCtrl for
                    # adding text. It seems that text modifications, even
                    # programmatically, are disabled in StyledTextCtrl when read
                    # only is active
                    start_pos = self.output.GetLength()
                    self.output.SetReadOnly(False)
                    self.output.AppendText(s)
                    self.output.SetReadOnly(True)
                    text_len = self.output.GetLength() - start_pos

                    if style != self.black_white:
                        self.output.SetStyling(text_len, style)
                self.stack = []
                self.StackLock.release()
                self.output.ScrollToEnd()
                self.output.Thaw()
                self.LastRefreshTime = gettime()
                newtime = time.time()
                if newtime - self.rising_timer > 1:
                    self.risecall(self.output)
                self.rising_timer = newtime
                self.refreshPending = False

    def write_warning(self, s):
        self.write(s, self.red_white)

    def write_error(self, s):
        self.write(s, self.red_yellow)

    def error(self, s):
        self.write(s, self.red_yellow)

    def warning(self, s):
        self.write(s, self.red_white)

    def info(self, s):
        self.write(s, self.black_white)

    def flush(self):
        # Temporary deactivate read only mode on StyledTextCtrl for clearing
        # text. It seems that text modifications, even programmatically, are
        # disabled in StyledTextCtrl when read only is active
        self.output.SetReadOnly(False)
        self.output.SetText("")
        self.output.SetReadOnly(True)

    def isatty(self):
        return False

    def progress(self, text):
        l = max(self.output.GetLineCount() - 2, 0)
        self.output.AnnotationSetText(l, text)
        self.output.AnnotationSetVisible(wx.stc.STC_ANNOTATION_BOXED)
        self.output.AnnotationSetStyle(l, self.black_white)
        if self.YieldLock.acquire(False):
            app = wx.GetApp()
            app.Yield()
            self.YieldLock.release()

    def info(self, s, style=None):
        self.write(s, style)

    def debug(self, s):
        self.write(s)

    def error(self, s):
        self.write_error(s)

    def warning(self, s):
        self.write_warning(s)
